using System.Collections.Generic;
using Game.Networking.Utils;
using UnityEngine;
using Utils;

namespace Game.Networking.Client {
	public sealed class ClientScriptingController : ClientsideController<ClientScriptingController> {
		public EventBus         OnCodeFileListUpdated     = new EventBus();
		public EventBus         OnUnitStateChanged        = new EventBus();
		public EventBus         OnTerminalCommandReceived = new EventBus();
		public EventBus<uint>   OnAttachedEntityIdChanged = new EventBus<uint>();
		public EventBus<string> OnFileUpdateReceived      = new EventBus<string>();

		ScriptHolder _codeFiles          = new ScriptHolder();
		List<string> _serverCodeFileList = new List<string>();

		ComputingUnitState _unitState;
		uint               _attachedEntityId;

		List<string> _terminalLines = new List<string>(ComputerTextTerminal.MAX_LINE_COUNT);

		public List<string> TerminalLines => _terminalLines;

		public ClientScriptingController(ClientGameManager owner) : base(owner) {
		}

		public override void Init() {
			base.Init();
			var persistentDataPath = GameManager.PersistentDataPath + "/Save/";
			string path = string.Format("{0}{1}/scripts/", persistentDataPath, WorldOptions.WorldName);
			_codeFiles.Init(path);
		}

		public List<string> GetCodeFileList() {
			return _codeFiles.GetFileNames();
		}

		public ComputingUnitState GetUnitState() {
			return _unitState;
		}

		public uint GetAttachedEntityId() {
			return _attachedEntityId;
		}

		public void TryRemoveFile(string filename) {
			if ( string.IsNullOrEmpty(filename) || !_codeFiles.ContainsKey(filename) ) {
				return;
			}

			_serverCodeFileList.Remove(filename);
			_codeFiles.Remove(filename);
			OnCodeFileListUpdated.Fire();
			ClientController.Instance.SendNetMessage(ClientPacketID.ProgramFileDeleteRequest,
				new C_FileRemoveRequestMessage() {FileName = filename});
		}

		public void SetAttachedEntityId(uint id) {
			_attachedEntityId = id;
			OnAttachedEntityIdChanged.Fire(id);
		}

		public void SetUnitState(ComputingUnitState newState) {
			_unitState = newState;
			OnUnitStateChanged?.Fire();
		}

		public string GetCodeFileContents(string fileName) {
			if ( string.IsNullOrEmpty(fileName) ) {
				return null;
			}

			if ( _codeFiles.TryGetValue(fileName, out var codeFile) ) {
				return codeFile;
			}

			return null;
		}

		public bool HasDownloadedCodeFile(string fileName) {
			if ( string.IsNullOrEmpty(fileName) ) {
				return false;
			}

			return _codeFiles.ContainsKey(fileName);
		}

		public void SetServerCodeFileList(List<string> files) {
			_serverCodeFileList = files;
		}

		public void SetCodeFile(string fileName, string contents) {
			if ( _codeFiles.ContainsKey(fileName) ) {
				_codeFiles[fileName] = contents;
			}
			else {
				_codeFiles.Add(fileName, contents);
			}

			OnFileUpdateReceived.Fire(fileName);
		}

		public void SaveAndUploadCodeFile(string fileName, string contents) {
			if ( string.IsNullOrEmpty(fileName) ) {
				return;
			}

			if ( !_codeFiles.TryGetValue(fileName, out var codeFile) ) {
				CreateNewCodeFile(fileName, contents);
				return;
			}

			_codeFiles[fileName] = contents;
			UploadCodeFile(fileName);
		}

		public void CreateNewCodeFile(string fileName, string initContents) {
			if ( _codeFiles.ContainsKey(fileName) || _serverCodeFileList.Contains(fileName) ) {
				return;
			}

			if ( initContents == null ) {
				initContents = string.Empty;
			}

			_codeFiles.Add(fileName, initContents);
			_serverCodeFileList.Add(fileName);
			UploadCodeFile(fileName);
			OnCodeFileListUpdated.Fire();
		}

		public void UploadAllCodeFiles() {
			foreach ( var file in _codeFiles.GetFiles() ) {
				ClientController.Instance.SendNetMessage(ClientPacketID.ProgramFileUpload,
					new C_ProgramFileUpdateMessage() {FileName = file.Item1, FileContents = file.Item2});
			}
		}

		public void UploadCodeFile(string fileName) {
			if ( !_codeFiles.TryGetValue(fileName, out var codeFile) ) {
				return;
			}

			_serverCodeFileList.Add(fileName);
			ClientController.Instance.SendNetMessage(ClientPacketID.ProgramFileUpload,
				new C_ProgramFileUpdateMessage() {FileName = fileName, FileContents = codeFile});
		}

		public void SendRecompileRequest() {
			ClientController.Instance.SendNetMessage(ClientPacketID.RecompileRequest, new C_RecompileRequestMessage());
		}

		public void TryDownloadFile(string fileName) {
			if ( string.IsNullOrEmpty(fileName) ) {
				return;
			}

			ClientController.Instance.SendNetMessage(ClientPacketID.ProgramFileDownloadRequest,
				new С_ProgramFileDownloadRequestMessage() {FileName = fileName});
		}

		public void TryDownloadAllFiles() {
			foreach ( var filename in _serverCodeFileList ) {
				TryDownloadFile(filename);
			}
		}

		public void SendTerminalMessage(string message) {
			ClientController.Instance.SendNetMessage(ClientPacketID.ProgramConsoleInput,
				new C_ProgramTerminalCommandMessage() {Message = message});
		}

		public void ProcessTerminalCommandFromServer(TerminalCommand command, string args) {
			Debug.Log($"{command} {args}");
			switch ( command ) {
				default:
				case TerminalCommand.None:
					break;
				case TerminalCommand.Clear:
					_terminalLines.Clear();
					break;
				case TerminalCommand.RemoveLine:
					if ( _terminalLines.Count == 0 ) {
						return;
					}

					if ( !int.TryParse(args, out var lineCount) ) {
						lineCount = 1;
					}

					lineCount = Mathf.Clamp(lineCount, 0, _terminalLines.Count);
					_terminalLines.RemoveRange(0, lineCount);
					break;
				case TerminalCommand.PrintLine:
					_terminalLines.Add(args);
					if ( _terminalLines.Count > ComputerTextTerminal.MAX_LINE_COUNT ) {
						_terminalLines.RemoveAt(0);
					}

					break;
			}

			OnTerminalCommandReceived.Fire();
		}
	}
}